/*
 * Quendor - A Z-Machine Interpreter for Android
 * Copyright (c) 2011 Michael Kleinhenz, michael@kleinhenz.net
 *
 * Quendor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 3 
 * as published by the Free Software Foundation.
 *
 * Quendor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Quendor.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.kleinhenz.quendor;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.logging.Logger;

import net.kleinhenz.quendor.ui.QCharacter;
import net.kleinhenz.quendor.ui.QView;
import net.kleinhenz.quendor.ui.QWindow;
import net.kleinhenz.quendor.zmachine.Dimension;
import net.kleinhenz.quendor.zmachine.Point;
import net.kleinhenz.quendor.zmachine.ZCPU;
import net.kleinhenz.quendor.zmachine.ZUserInterface;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.TextView;

public class QuendorActivity extends Activity implements ZUserInterface, OnKeyListener {
	
	Logger log = Logger.getLogger("Quendor");
	
	public static final int DEFAULT_FOREGROUND = COLOR_BLACK;
	public static final int DEFAULT_BACKGROUND = COLOR_WHITE;	
	public static final int DEFAULT_STYLE = STYLE_ROMAN;
	public static final int DEFAULT_FONT = 1;	
	
	private List<QWindow> windows = null;
	private int currentWindow = -1;
	private int currentBG = DEFAULT_BACKGROUND;
	private int currentFG = DEFAULT_FOREGROUND;
	private int currentStyle = DEFAULT_STYLE;
	private int currentFont = DEFAULT_FONT;
	
	private ZCPU cpu = null;
	
	private TextView statusbar = null;
	private QView contentarea = null;
	
	private StringBuilder inputBuffer = new StringBuilder();
	private boolean inputBufferCommit = false;
	
	public static String uuid() {
		String uuid = UUID.randomUUID().toString();
		return "" + UUID.randomUUID().toString().substring(uuid.length()-5, uuid.length()-1);
	}
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
		log.info("Creating new Quendor activity.");

    	super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
     
        // Z-Machine allows for 8 screens max
        windows = Collections.synchronizedList(new ArrayList<QWindow>(8));
        
        statusbar = (TextView)findViewById(R.id.statusbar);
        statusbar.setTextSize(QView.TYPESIZE);
        statusbar.setHeight(statusbar.getLineHeight() * 2);
        contentarea = (QView)findViewById(R.id.contentarea);

        // create initial window
        splitScreen(-1);

        // load and start story
        AssetManager assetManager = getAssets();
        try {
			InputStream is = assetManager.open("demo.z5");
	        cpu = new ZCPU(this);
	        cpu.initialize(is);
	        cpu.start();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
    }	

	private QWindow createScreen(int lines) {
		log.info("Creating new screen with " + lines  + " lines.");
		QWindow window = new QWindow();
		window.screen = new ArrayList<List<QCharacter>>();
		window.cursor = new Point(0, 0);
		window.height = lines;
		window.width = contentarea.getTerminalWidth();
		return window;
	}

	private void updateView(final int window, final String uuid) {
		contentarea.post(new Runnable() {
	        public void run() {
	        	contentarea.updateWindow(windows.get(window), uuid);
	        }
	      });
	}

	// ZUserInterface Methods
	
	@Override
	public boolean hasStatusLine() {
		return true;
	}

	@Override
	public boolean defaultFontProportional() {
		return true;
	}

	@Override
	public boolean hasColors() {
		return true;
	}

	@Override
	public boolean hasBoldface() {
		return true;
	}

	@Override
	public boolean hasItalic() {
		return true;
	}

	@Override
	public boolean hasFixedWidth() {
		return true;
	}

	@Override
	public boolean hasTimedInput() {
		return true;
	}

	@Override
	public void initialize(int ver) {
		// NOP
	}

	@Override
	public boolean hasUpperWindow() {
		return true;
	}

	@Override
	public void fatal(final String errmsg) {
		statusbar.post(new Runnable() {
	        public void run() {
	    		AlertDialog.Builder builder = new AlertDialog.Builder(QuendorActivity.this);
	    		builder.setTitle("Fatal Error")
	    			   .setMessage(errmsg)
	    		       .setCancelable(false)
	    		       .setNeutralButton("Ok", new DialogInterface.OnClickListener() {
	    		           public void onClick(DialogInterface dialog, int id) {
	    		                QuendorActivity.this.finish();
	    		           }
	    		       });
	    		builder.create();
	        }
	      });
	}

	@Override
	public Dimension getScreenCharacters() {
		return new Dimension(contentarea.getTerminalWidth(), contentarea.getTerminalHeight());
	}

	@Override
	public Dimension getScreenUnits() {
		return new Dimension(contentarea.getTerminalWidth(), contentarea.getTerminalHeight());
	}

	@Override
	public Dimension getFontSize() {
		return new Dimension(1, 1);
	}

	@Override
	public Dimension getWindowSize(int window) {
		return new Dimension(windows.get(window).width, windows.get(window).height);
	}

	@Override
	public int getDefaultForeground() {
		return DEFAULT_FOREGROUND;
	}

	@Override
	public int getDefaultBackground() {
		return DEFAULT_BACKGROUND;
	}

	@Override
	public void setTerminatingCharacters(Vector<Integer> chars) {
		// TODO find out what this means
	}
	
	@Override
	public Point getCursorPosition() {
		// I suppose this means in the current window
		return windows.get(currentWindow).cursor;
	}

	@Override
	public void showStatusBar(final String s, final int a, final int b, final boolean flag) {
		log.info("ZCPU Updating content of status bar: " + s.trim());
		statusbar.post(new Runnable() {
	        public void run() {
	        	StringBuffer text = new StringBuffer(s);
	        	if (flag) {
	        		text.append(a);
	        		text.append(':');
	        		text.append(b);       		
	        	}
	    		statusbar.setText(text.toString());
	        }
	      });
	}

	@Override
	public void splitScreen(int lines) {
		log.info("ZCPU splitting screen with " + lines  + " lines. Current window is " + currentWindow + ".");
		final QWindow window = createScreen(lines);
		window.id = windows.size();
		windows.add(window);

		contentarea.post(new Runnable() {
	        public void run() {
	    		contentarea.createScreen(QuendorActivity.this, currentWindow, window);
	        }
	      });

		currentWindow = windows.size()-1;
	}

	@Override
	public void setCurrentWindow(int window) {
		log.info("ZCPU setting current window " + window  + ".");
		currentWindow = window;
	}

	@Override
	public void setCursorPosition(int x, int y) {
		log.info("ZCPU setting cursor to " + x  + "/" + y + ". Current window is " + currentWindow + ".");
		// I suppose this means in the current window
		windows.get(currentWindow).cursor.x = x;
		windows.get(currentWindow).cursor.y = y;
	}

	@Override
	public void setColor(int fg, int bg) {
		currentFG = fg;
		currentBG = bg;
	}

	@Override
	public void setTextStyle(int style) {
		switch (style) {
			case ZUserInterface.STYLE_BOLD: log.info("ZCPU setting text style to bold."); break;
			case ZUserInterface.STYLE_FIXED: log.info("ZCPU setting text style to fixed."); break;
			case ZUserInterface.STYLE_ITALICS: log.info("ZCPU setting text style to italics."); break;
			case ZUserInterface.STYLE_REVERSE: log.info("ZCPU setting text style to reverse."); break;
			case ZUserInterface.STYLE_ROMAN: log.info("ZCPU setting text style to roman."); break;
		}		
		currentStyle = style;
	}

	@Override
	public void setFont(int font) {
		switch (font) {
			case ZUserInterface.FONT_CHARGRAPHICS: log.info("ZCPU setting font to character graphics font."); break;
			case ZUserInterface.FONT_FIXED: log.info("ZCPU setting font to fixed font."); break;
			case ZUserInterface.FONT_NORMAL: log.info("ZCPU setting font to normal font."); break;
			case ZUserInterface.FONT_PICTURE: log.info("ZCPU setting font to picture font."); break;
		}		
		currentFont = font;
	}
	
	// FIXME: implement input

	@Override
	public int readLine(StringBuffer sb, int time) {
		log.info("ZCPU reading line with timeout=" + time + ".");
		// FIXME implement time
		while (!inputBufferCommit)
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}

		sb.append(inputBuffer);
		inputBuffer.setLength(0);
		inputBufferCommit = false;
		return sb.length();
	}

	@Override
	public int readChar(int time) {
		log.info("ZCPU reading character with timeout=" + time + ".");
		// FIXME implement time
		while (!inputBufferCommit)
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}

		int data = inputBuffer.charAt(0);
		inputBuffer.setLength(0);
		inputBufferCommit = false;
		return data;
	}

	@Override
	public String getFilename(String title, String suggested, boolean saveFlag) {
		// FIXME: implement file chooser
		return null;
	}

	@Override
	public void quit() {
		contentarea.post(new Runnable() {
	        public void run() {
	        	QuendorActivity.this.finish();
	        }
	      });
	}

	@Override
	public void restart() {
		// FIXME: implement restart
	}
	
	@Override
	public void eraseWindow(int window) {
		final String uuid = QuendorActivity.uuid();
		log.info("ZCPU erases window " + window + " (" + uuid + ".");
		int lines = windows.get(window).height;
		windows.set(window, createScreen(lines));
		updateView(currentWindow, uuid);
	}

	@Override
	public void eraseLine(int s) {
		final String uuid = QuendorActivity.uuid();
		log.info("ZCPU erases line " + s + ". Current window is " + currentWindow + " (" + uuid + ".");
		windows.get(currentWindow).screen.set(s, new ArrayList<QCharacter>());
		updateView(currentWindow, uuid);
	}

	@Override
	public void showString(final String s) {	
		final String uuid = QuendorActivity.uuid();
		log.info("ZCPU shows text. Current window is " + currentWindow + " (" + uuid + ").");
		contentarea.post(new Runnable() {
	        public void run() {
	        	showStringInternal(s);
	        }
	      });
		
		updateView(currentWindow, uuid);
	}

	private void showStringInternal(final String s) {

		QWindow window = windows.get(currentWindow);

		if (window.type==QWindow.Type.RASTER) {
			// raster window, do x/y math
			Point cursor = windows.get(currentWindow).cursor;
			for (int i=0; i<s.length(); i++) {
				QCharacter ch = genChar(s.charAt(i));
				
				if (window.screen.size()==cursor.y)
					window.screen.add(new ArrayList<QCharacter>());
				List<QCharacter> currentLine = window.screen.get(cursor.y);
				if (currentLine.size()==cursor.x)
					currentLine.add(ch);
				else
					currentLine.set(cursor.x, ch);
				cursor.x++;
				if (cursor.x==getWindowSize(currentWindow).width) {
					cursor.x = 0;
					cursor.y++;
				}
				if (cursor.y==getWindowSize(currentWindow).height) {
					// FIXME scroll up
					window.screen.remove(0);
					window.screen.add(new ArrayList<QCharacter>());
					cursor.y--;
				}
			}
		} else {
			// flow window, just add text to the screen buffer
			List<QCharacter> qcl = new ArrayList<QCharacter>();
			for (int i=0; i<s.length(); i++) {
				QCharacter ch = genChar(s.charAt(i));
				qcl.add(ch);
			}
			window.screen.add(qcl);
		}
	}

	@Override
	public void scrollWindow(int lines) {
		final String uuid = QuendorActivity.uuid();
		log.info("ZCPU scrolls window for " + lines + " lines. Current window is " + currentWindow + " (" + uuid + ".");
		for (int i=0; i<lines; i++) {
			if (windows.get(currentWindow).type==QWindow.Type.RASTER)
				windows.get(currentWindow).screen.remove(0);
			QCharacter ch = genChar('\n');
			windows.get(currentWindow).screen.add(Collections.singletonList(ch));				
			}
		updateView(currentWindow, uuid);
	}

	private QCharacter genChar(char c) {
		QCharacter ch = new QCharacter();
		ch.bg = currentBG;
		ch.fg = currentFG;
		ch.tstyle = currentStyle;
		ch.font = currentFont;
		ch.c = c;
		return ch;
	}

	@Override
	public boolean onKey(View v, int keyCode, KeyEvent event) {
		// FIXME filter chars
		log.info("KEY: " + keyCode);
		if (keyCode==66)
			inputBufferCommit = true;
		else
			inputBuffer.append(keyCode);
		return false;
	}
}